package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.UserFeignClient;
import com.atguigu.gmall.cart.client.CartFeignClient;
import com.atguigu.gmall.cart.model.CartInfo;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.enums.model.OrderStatus;
import com.atguigu.gmall.enums.model.ProcessStatus;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.javafx.collections.MappingChange;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 购物车表 用户登录系统时更新冗余 业务实现类
 *
 * @author atguigu
 * @since 2023-05-07
 */
@Service
@Slf4j
@SuppressWarnings("all")
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper,OrderInfo> implements OrderInfoService {

    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private CartFeignClient cartFeignClient;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private OrderDetailService orderDetailService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private RabbitService rabbitService;

    @Value("${ware.url}")
    private String wareUrl;
    /**
     * 汇总订单确认页面需要的数据
     * @param userId 用户id
     * @return
     */
    @Override
    public Map<String, Object> trade(Long userId) {
        HashMap<String, Object> mapResult = new HashMap<>();
        //1.调用用户微服务获取用户收货地址列表
        List<UserAddress> userAddressList = userFeignClient.getUserAddressListByUserId(userId);
        if(!CollectionUtils.isEmpty(userAddressList)){
            mapResult.put("userAddressList", userAddressList);
        }

        //2.调用购物车微服务获取需要结算的购物车商品列表
        List<CartInfo> cartCheckedList = cartFeignClient.getCartCheckedList(userId);
        if(!CollectionUtils.isEmpty(cartCheckedList)){
            //2.1将集合泛型从购物车转为订单明细类型
            List<OrderDetail> orderDetailsList = cartCheckedList.stream().map(cartInfo -> {
                OrderDetail orderDetail = new OrderDetail();
                BeanUtils.copyProperties(cartInfo, orderDetail);
                //2.2单独设置价格属性
                orderDetail.setOrderPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
                return orderDetail;
            }).collect(Collectors.toList());
            mapResult.put("detailArrayList", orderDetailsList);
            //3.封装${totalNum}订单商品数量
            mapResult.put("totalNum", orderDetailsList.size());
            //4.订单$(totalAmount)总金额
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setOrderDetailList(orderDetailsList);
            orderInfo.sumTotalAmount();
            mapResult.put("totalAmount",orderInfo.getTotalAmount());
        }
        String tradeNo = generateTradeNo(userId.toString());
        mapResult.put("tradeNo", tradeNo);
        return mapResult;
    }

    /**
     * 提交保存订单
     * @param orderInfo 订单信息
     * @param tradeNo 流水号
     * @return
     */
    @Override
    public Long submitOrder(OrderInfo orderInfo, String tradeNo) {
        //采用lua脚本保证判断流水号 删除流水号原子性
        String luaScriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScriptText);
        redisScript.setResultType(Long.class);
        String tradeKey = "tradeNo:" + orderInfo.getUserId();
        Long flag = (Long) redisTemplate.execute(redisScript, Arrays.asList(tradeKey), tradeNo);
        if (flag.intValue() == 0) {
            throw new RuntimeException("请勿重复提交订单!");
        }
        //2.验证商品库存是否充足(远程调用第三方库存系统) todo
        //3.验证订单中商品价格是否为发生变化(远程调用商品服务获取) todo
        //2.1 获取订单中所有订单商品明细 遍历商品 验证库存 验证价格
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        //定义错误信息集合
        List<String> errorMsgList = new ArrayList<>();
        //定义异步任务集合
        ArrayList<CompletableFuture> allCompletableFutures = new ArrayList<>();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            for (OrderDetail orderDetail : orderDetailList) {
                //2.2 远程调用第三方库存系统获取库存是否冲突 http://localhost:9001/hasStock?skuId=20&num=7
                CompletableFuture<Void> stockComplatableFuture = CompletableFuture.runAsync(() -> {
                    String skuWareUrl = wareUrl + "/hasStock?skuId=" + orderDetail.getSkuId() + "&num=" + orderDetail.getSkuNum();
                    String hashStock = HttpClientUtil.doGet(skuWareUrl);
                    if (hashStock.equals("0")) {
                        //throw new RuntimeException("商品库存不足");
                        errorMsgList.add("库存不足" + orderDetail.getSkuName());
                    }
                }, threadPoolExecutor);
                allCompletableFutures.add(stockComplatableFuture);

                //2.3 远程调用商品微服务获取商品价格是否为最新
                CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
                    log.info("验证价格:{}", orderDetail.getSkuId());
                    BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
                    if (skuPrice.compareTo(orderDetail.getOrderPrice()) != 0) {
                        //2.4 更新当前用户购物车中缓存价格更新为最新
                        String cartKey = RedisConst.USER_KEY_PREFIX + orderInfo.getUserId() + RedisConst.USER_CART_KEY_SUFFIX;
                        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
                        CartInfo cartInfo = hashOps.get(orderDetail.getSkuId().toString());
                        if (cartInfo != null) {
                            cartInfo.setSkuPrice(skuPrice);
                            hashOps.put(orderDetail.getSkuId().toString(), cartInfo);
                        }
                        //throw new RuntimeException("订单中商品价格失效!");
                        //收集价格失效商品
                        errorMsgList.add("价格失效:" + orderDetail.getSkuName());
                    }
                }, threadPoolExecutor);
                allCompletableFutures.add(priceCompletableFuture);
            }

            //执行所有异步任务
            CompletableFuture.allOf(allCompletableFutures.toArray(new CompletableFuture[allCompletableFutures.size()])).join();

            //判断错误集合是否有数据
            if(!CollectionUtils.isEmpty(errorMsgList)){
                String errorMsg = errorMsgList.stream().collect(Collectors.joining(","));
                throw new RuntimeException(errorMsg);
            }
        }
        //4.保存订单,保存订单明细
        Long orderId = this.saveOrderInfo(orderInfo);
        //5.发送延迟关闭消息到RabbitMQ,通知订单微服务,验证订单状态,修改订单状态
        rabbitService.sendDelayMessage(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL,MqConst.ROUTING_ORDER_CANCEL, orderId,30);
        return orderId;
    }

    /**
     * 保存订单信息
     * @param orderInfo 订单信息
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Long saveOrderInfo(OrderInfo orderInfo) {
        //1.保存订单信息
        //1.1为订单对象中部分属性赋值
        orderInfo.sumTotalAmount();
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //生成订单唯一编号,该编号用于支付
        String outTradeNo = "SPH" + System.currentTimeMillis();
        orderInfo.setOutTradeNo(outTradeNo);
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if(!CollectionUtils.isEmpty(orderDetailList)){
            //默认照片显示为商品图片
            orderInfo.setImgUrl(orderDetailList.get(0).getImgUrl());
            String tradeBody = orderDetailList.stream().map(OrderDetail::getSkuName).collect(Collectors.joining(","));
            if(tradeBody.length() > 100){
                tradeBody.substring(0,100);
            }
            orderInfo.setTradeBody(tradeBody);
        }
        orderInfo.setOperateTime(new Date());
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, 1);
        orderInfo.setExpireTime(calendar.getTime());
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        //todo 从提交用户收件地址中获取  得到用户收件地址详细信息获取
        orderInfo.setProvinceId(1L);

        //1.2执行保存订单
        this.save(orderInfo);
        Long orderId = orderInfo.getId();
        //1.3保存订单明细
        orderDetailList.stream().forEach(orderDetail -> {
            orderDetail.setOrderId(orderId);
            //商城订单 1sourceId
            orderDetail.setSourceId(1L);
            orderDetail.setSourceType("MALL");
        });

        //批量保存
        orderDetailService.saveBatch(orderDetailList);
        return orderId;
    }


    /**
     * 生成流水号
     * @param userId 用户id
     * @return
     */
    @Override
    public String generateTradeNo(String userId) {
        String tradeNo = UUID.randomUUID().toString().replace("-", "");
        String tradeKey = "tradeNo:" + userId;
        redisTemplate.opsForValue().set( tradeKey,tradeNo);
        return tradeNo;
    }

    /**
     * 验证流水号是否一致
     * @param userId 用户id
     * @param tradeNo 流水号
     * @return
     */
    @Override
    public boolean checkTradeNo(String userId, String tradeNo) {
        String tradeKey = "tradeNo:" + userId;
        String redisTradeNo = (String) redisTemplate.opsForValue().get(tradeKey);
        return tradeNo.equals(redisTradeNo);
    }

    /**
     * 删除业务流水号
     * @param userId 用户id
     */
    @Override
    public void deleteTradeNo(String userId) {
        String tradeKey = "tradeNo:" +userId;
        redisTemplate.delete(tradeKey);
    }

    /**
     * 分页获取当前用户订单列表
     * @param infoPage
     * @param userId 用户id
     * @return
     */
    @Override
    public Page<OrderInfo> getOrderList(Page<OrderInfo> infoPage, String userId) {
        //方式1;用Wrapper 先查询当前所有用户订单,遍历订单,在查询当前订单中的明细
        //方式2;自定义sql
        OrderInfoMapper orderInfoMapper = this.getBaseMapper();
        infoPage = orderInfoMapper.getOrderList(infoPage,userId);
        return infoPage;
    }

    /**
     * 关闭订单
     * @param orderId 订单id
     */
    @Override
    public void execExpiredOrder(Long orderId) {
        this.updateOrderStatus(orderId, ProcessStatus.CLOSED);
    }

    /**
     * 根据订单id更新订单状态
     * @param orderId 订单id
     * @param processStatus
     */
    @Override
    public void updateOrderStatus(Long orderId, ProcessStatus processStatus) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setProcessStatus(processStatus.name());
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        this.updateById(orderInfo);
    }

    /**
     * 根据orderId查询订单信息,包含订单明细
     * @param orderId 订单id
     * @return 查询到的订单id
     */
    @Override
    public OrderInfo getOrderInfoByOrderId(Long orderId) {
        //1.根据主键查询订单信息
        OrderInfo orderInfo = this.getById(orderId);
        if(orderInfo != null){
            //2.根据订单id,查订单明细
            LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(OrderDetail::getOrderId,orderId);
            List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);
        }
        return orderInfo;
    }

    /**
     * 通知第三方锁定库存
     * @param orderId 订单id
     */
    @Override
    public void sendDeductLockMsg(Long orderId) {
        //1.根据订单id查询订单信息(包含订单明细)
        OrderInfo orderInfo = this.getOrderInfoByOrderId(orderId);
        if(orderInfo != null){
            //2.按照第三方库存要求接口参数封装
            Map map = this.initWareMap(orderInfo);
            //3.发送消息到MQ完成锁定库存
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_WARE_STOCK, MqConst.ROUTING_WARE_STOCK, JSON.toJSONString(map));
        }
    }

    /**
     * 构建第三方库存系统要求参数(订单信息,订单明细信息)
     * @param orderInfo 订单信息
     * @return 锁定库存结果
     */
    private Map initWareMap(OrderInfo orderInfo) {
        //1,构建结果
        HashMap<String, Object> map = new HashMap<>();
        //1.1封装订单信息
        map.put("orderId", orderInfo.getId()); //订单id
        map.put("consignee", orderInfo.getConsignee()); //收货人
        map.put("consigneeTel",orderInfo.getConsigneeTel()); //收货人电话
        map.put("orderComment",orderInfo.getOrderComment()); //订单评论
        map.put("orderBody", orderInfo.getTradeBody());// 订单内容
        map.put("deliveryAddress", orderInfo.getDeliveryAddress()); //收货人地址

        map.put("paymentWay", 1); //支付方式1:在线支付
        //1.2封装订单明细
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if(!CollectionUtils.isEmpty(orderDetailList)){
            List<HashMap<String,Object>> details = orderDetailList.stream().map(orderDetail -> {
                HashMap<String, Object> detailMap = new HashMap<>();
                detailMap.put("skuId", orderDetail.getSkuId());
                detailMap.put("skuNum", orderDetail.getSkuNum());
                detailMap.put("skuName", orderDetail.getSkuName());
                return detailMap;
            }).collect(Collectors.toList());
            map.put("details", details);
        }
        return map;
    }

    /**
     * 提供给库存系统拆单业务接口
     * 本质:将原始订单以及订单明细,拆分为多个子订单(新增),和子订单明细(新增)
     * @param orderId 订单id
     * @param wareSkuMap 系统查询到订单id和商品的对照关系, [{"wareId":"1","skuIds":["41"]},{"wareId":"2","skuIds":["26"]}]
     * @return 拆单后结果,包含每个订单对应每个仓库的id
     */
    @Override
    public String orderSplit(String orderId, String wareSkuMapStr) {
        //1.根据原始订单id,查询原始订单信息,原始订单明细
        if(StringUtils.isBlank(orderId) && StringUtils.isNotBlank(wareSkuMapStr)){
            OrderInfo sourceOrderInfo = this.getOrderInfoByOrderId(Long.valueOf(orderId));
            if(sourceOrderInfo != null && OrderStatus.SPLIT.name().equals(sourceOrderInfo.getOrderStatus())){
                //原始订单明细
                List<OrderDetail> sourceOrderDetailList = sourceOrderInfo.getOrderDetailList();
                //2.将仓库id和商品id对照json字符串转为?List集合的Map类型
                List<Map> allWareSkuList = JSON.parseArray(wareSkuMapStr, Map.class);
                if(!CollectionUtils.isEmpty(allWareSkuList)){
                    //3.遍历集合,遍历每一次产生子订单,得到子订单明细,将子订单明细和子订单新增
                    List<OrderInfo> allSubOrderInfoList = allWareSkuList.stream().map(wareSkuMap -> {

                        //3.1新建子订单对象,大概多数属性来自于原始订单
                        OrderInfo subOrderInfo = new OrderInfo();
                        BeanUtils.copyProperties(sourceOrderInfo, subOrderInfo);
                        subOrderInfo.setId(null);//避免分布式id出现主键冲突
                        subOrderInfo.setParentOrderId(sourceOrderInfo.getId());
                        subOrderInfo.setUpdateTime(new Date());

                        //3.2判断得到新子订单中中订单明细
                        //3.2.1获取当前子订单只能怪商品id集合
                        List<String> skuIdStrList = (List<String>) wareSkuMap.get("skuIds");
                        //3.2.3遍历原始订单明细集合将当前子订单中订单明细过滤出来
                        List<OrderDetail> newSubOrderDetailList = sourceOrderDetailList.stream().filter(originOrderDetail -> {
                            return skuIdStrList.contains(originOrderDetail.getSkuId().toString());
                        }).collect(Collectors.toList());
                        //3.2.4给新的订单设置对应出货仓库id
                        String wareId = (String) wareSkuMap.get("wareId");
                        subOrderInfo.setWareId(wareId);
                        //3.3保存新子订单
                        this.save(subOrderInfo);

                        //3.4 todo 保存订单明细
                        newSubOrderDetailList.stream().forEach(newSubOrderDetail -> {
                            //将新增子订单关联到订单明细
                            newSubOrderDetail.setOrderId(subOrderInfo.getId());
                        });
                        orderDetailService.saveBatch(newSubOrderDetailList);
                        return subOrderInfo;
                    }).collect(Collectors.toList());

                    //3.3.1将原始订单状态改为:已拆单
                    sourceOrderInfo.setOrderStatus(OrderStatus.SPLIT.name());
                    this.updateById(sourceOrderInfo);

                    //4.按照接口文档响应结果给库存系统 todo 保证每个订单包含对应出货仓库id
                    List<Map> orderWareMap = allSubOrderInfoList.stream().map(subOrderInfo -> {
                        return this.initWareMap(subOrderInfo);
                    }).collect(Collectors.toList());
                    return JSON.toJSONString(orderWareMap);
                }
            }
        }
        return null;
    }

    /**
     * 将订单关闭
     * @param orderId
     */
    public void execExpireOrder(Long orderId){
        //1.关闭订单状态
        this.updateOrderStatus(orderId, ProcessStatus.CLOSED);
        //2.新增发送消息给支付系统关闭交易记录
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, MqConst.ROUTING_PAYMENT_CLOSE,orderId);
    }
}